#include "stdafx.h"
#include "CopyConverter.h"
#include "FileHandler.h"
#include <assert.h>
#include <codecvt>
#include <limits>

static const DWORD kErrorCodeInsufficientBuffer = (1 << 29) + 10;
static const int kBytesPerRowInHexDigitsMode = 20;
static const int kApproximateLineLengthLimitInCStyleMode = 70;

// -----------------------------------------------------------

CopyConverter::CopyConverter()
{
	mDataHandle = 0;
	mCurrentPos = 0;
	mClipboardSize = 0;
}

CopyConverter::~CopyConverter()
{
	if (mDataHandle)
	{
		::GlobalFree(mDataHandle);
		mDataHandle = 0;
	}
}

LONGLONG CopyConverter::GetWrittenDataLength() const
{
	return mClipboardSize;
}

bool CopyConverter::IsWideCharacterData() const
{
	return false;
}

// protected
bool CopyConverter::CreateDataHandle(LONGLONG inSize)
{
	if (sizeof(inSize) >= sizeof(SIZE_T) &&
		inSize > SIZE_MAX)
	{
		return false;
	}
	mDataHandleSize = SIZE_T(inSize);
	mDataHandle = ::GlobalAlloc(GMEM_MOVEABLE, mDataHandleSize);
	return (0 != mDataHandle);
}

bool CopyConverter::WriteTerminatingZero()
{
	bool result = false;
	int length = IsWideCharacterData() ? sizeof(WCHAR) : sizeof(char);
	if (auto p = GetBufferOfSize(length))
	{
		::memset(p, '\0', length);
		SaveUpToBufferOffset(length);
		result = true;
	}

	return result;
}

// -----
bool CopyConverterFixed::InitWithSelectionLength(LONGLONG inSelectionLength,
	LONGLONG& outMinDataLength, LONGLONG& outMaxDataLength)
{
	bool result = false;
	LONGLONG len = CalculateDataLength(inSelectionLength);
	if (len <= SIZE_MAX && CreateDataHandle(len))
	{
		mDataPtr = reinterpret_cast<BYTE*>(::GlobalLock(mDataHandle));
		if (nullptr != mDataPtr)
		{
			outMinDataLength = outMaxDataLength = len;
			result = true;
		}
	}
	return result;
}

BYTE* CopyConverterFixed::GetBufferOfSize(int /*size*/)
{
	assert(nullptr != mDataPtr);
	return mDataPtr + mClipboardSize;
}
void CopyConverterFixed::SaveUpToBufferOffset(int offset)
{
	mClipboardSize += offset;
}

HGLOBAL CopyConverterFixed::GiveData()
{
	assert(nullptr != mDataPtr);
	HGLOBAL result = 0;
	
	if (nullptr != mDataPtr && WriteTerminatingZero())
	{
		BOOL b = ::GlobalUnlock(mDataHandle);
		if (b || NO_ERROR == ::GetLastError())
		{
			result = mDataHandle;
			mDataHandle = 0;
		}
	}

	return result;
}

// ------
CopyConverterExpandable::~CopyConverterExpandable()
{
	for (auto p : mMemoryBlocks)
	{
		::HeapFree(mHeap, 0, p);
	}
	mMemoryBlocks.clear();
}

bool CopyConverterExpandable::InitBuffersForSizes(LONGLONG inMinSize,
	LONGLONG inMaxSize)
{
	mHeap = ::GetProcessHeap();
	mMemoryBlocks.reserve(size_t((inMaxSize - inMinSize) / kBlockSize));
	mCountOfUsedBlocks = 0;
	return (0 != mHeap) && CreateDataHandle(inMinSize);
}

BYTE* CopyConverterExpandable::GetBufferOfSize(int inSize)
{
	BYTE* result = nullptr;
	assert(kBlockSize > inSize);
	mWasBlockWrittenOutOfPlace = false;

	if (mClipboardSize + inSize <= mDataHandleSize)
	{
		if (auto p = reinterpret_cast<BYTE*>(::GlobalLock(mDataHandle)))
		{
			result = p + mClipboardSize;
		}
	}
	else
	{
		if (mMemoryBlocks.empty())
		{
			if (auto p = reinterpret_cast<BYTE*>(::HeapAlloc(mHeap, 0, kBlockSize)))
			{
				mWasBlockWrittenOutOfPlace = true;
				mMemoryBlocks.push_back(p);
				result = p;
			}
		}
		else if (0 < mCountOfUsedBlocks && mPositionInLastBlock + inSize <= kBlockSize)
		{
			result = mMemoryBlocks[mCountOfUsedBlocks - 1] + mPositionInLastBlock;
		}
		else
		{
			mWasBlockWrittenOutOfPlace = true;
			if (mCountOfUsedBlocks < mMemoryBlocks.size())
			{
				result = mMemoryBlocks[mCountOfUsedBlocks];
			}
			else if (auto p = reinterpret_cast<BYTE*>(::HeapAlloc(mHeap, 0, kBlockSize)))
			{
				mMemoryBlocks.push_back(p);
				result = p;
			}
		}
	}

	// make sure there were no problems concerning the no memory allocation
	assert(nullptr != result);
	return result;
}

void CopyConverterExpandable::SaveUpToBufferOffset(int offset)
{
	assert(0 < offset);
	if (mMemoryBlocks.empty())
	{
		::GlobalUnlock(mDataHandle);
	}
	else if (0 == mCountOfUsedBlocks)
	{
		auto pBuf = mMemoryBlocks.front();
		if (auto p = reinterpret_cast<BYTE*>(::GlobalLock(mDataHandle)))
		{
			int len1 = int( min(offset, int(mDataHandleSize - mClipboardSize)));
			assert(0 < len1);
			::memcpy(p + mClipboardSize, pBuf, len1);
			if (int len2 = offset - len1)
			{
				::memmove(pBuf, pBuf + len1, len2);
				mCountOfUsedBlocks = 1;
				mPositionInLastBlock = len2;
			}
			::GlobalUnlock(mDataHandle);
		}	
	}
	else if (!mWasBlockWrittenOutOfPlace)
	{
		mPositionInLastBlock += offset;
	}
	else
	{
		assert(mCountOfUsedBlocks < mMemoryBlocks.size());
		auto pBuf1 = mMemoryBlocks[mCountOfUsedBlocks - 1];
		auto pBuf2 = mMemoryBlocks[mCountOfUsedBlocks];
		int len1 = min(offset, kBlockSize - mPositionInLastBlock);
		::memcpy(pBuf1 + mPositionInLastBlock, pBuf2, len1);
		if (int len2 = offset - len1)
		{
			::memmove(pBuf2, pBuf2 + len1, len2);
			mPositionInLastBlock = len2;
			++mCountOfUsedBlocks;
		}
		else
		{
			mPositionInLastBlock += offset;
		}
	}
	mClipboardSize += offset;
}

HGLOBAL CopyConverterExpandable::GiveData()
{
	if (!WriteTerminatingZero())
	{
		return 0;
	}
	if (0 == mCountOfUsedBlocks)
	{
		auto handle = mDataHandle;
		mDataHandle = 0;
		return handle;
	}

	HGLOBAL result = 0;

	LONGLONG len = mDataHandleSize + 
		(mCountOfUsedBlocks - 1) * LONGLONG(kBlockSize) + 
		mPositionInLastBlock;
	assert(len == mClipboardSize);
	if (len <= SIZE_MAX)
	{
		if (auto newHandle = ::GlobalReAlloc(mDataHandle,
			SIZE_T(len), GMEM_MOVEABLE))
		{
			mDataHandle = 0;
			if (auto p = reinterpret_cast<BYTE*>(::GlobalLock(newHandle)))
			{
				p += mDataHandleSize;
				for (size_t i = 0; i < mCountOfUsedBlocks; ++i)
				{
					int len = (mCountOfUsedBlocks - 1 == i ?
						mPositionInLastBlock : kBlockSize);
					::memcpy(p, mMemoryBlocks[i], len);
					p += len;
				}
				::GlobalUnlock(newHandle);
				result = newHandle;
			}
			else
			{
				::GlobalFree(newHandle);
			}
		}
	}
	return result;
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////


// -------- private ----------

// ----

////////////////////////////////////////////////////////////////////
// protected

/////////////////////////////////////////////////////////////////////
// the several functions below are the faster alternatives to using std::snprintf()
// The speed is essential when there are big data for copying...

static const BYTE kHexDigits[] = "0123456789abcdef";
static BYTE* WriteAddress(BYTE* text, LONGLONG address)
{
	int numDigits = 0;
	for (auto x = address; x; x >>= 4)
	{
		++numDigits;
	}
	if (numDigits < 4)
	{
		numDigits = 4;
	}
	auto x = address;
	for (BYTE* p = text + numDigits - 1; p >= text; --p, x >>= 4)
	{
		*p = kHexDigits[x & 15];
	}
	return text + numDigits;
}

static BYTE* WriteByteHex(BYTE* text, BYTE b)
{
	*text++ = kHexDigits[(b >> 4) & 15];
	*text++ = kHexDigits[b & 15];
	return text;
}
static BYTE* WriteByteOct(BYTE* text, BYTE b)
{
	*text++ = ((b >> 6) & 7) + '0';
	*text++ = ((b >> 3) & 7) + '0';
	*text++ = (b & 7) + '0';
	return text;
}

/////////////////////////////////////////////////////////////////////
CopyConverterWysiwyg::CopyConverterWysiwyg(LONGLONG inStartAddress)
{
	mStartAddress = inStartAddress;
}

LONGLONG CopyConverterWysiwyg::CalculateDataLength(LONGLONG inInputLength) const
{
	LONGLONG result = 0;
	assert(0 < inInputLength);
	LONGLONG start = mStartAddress;
	LONGLONG end = mStartAddress + inInputLength - 1;

	// ": " + "XX XX ..." + " -" + "  " + "cccccccccccccccc" + "\r\n"
	static const int kLineLen0 = 2 + 3 * 16 + 2 + 2 + 16 + 2;
	ULONGLONG linesCount = (end >> 4) - (start >> 4) + 1;
	assert(0 < linesCount);
	result = linesCount * kLineLen0;

	// adding place for hex digits of addresses

	ULONGLONG a = start >> 4;
	int digitsInAddr = 1;
	for (auto x = a; x >>= 4;)
	{
		++digitsInAddr;
	}

	ULONGLONG roundNumber = 1LL << (4 * digitsInAddr);
	while (0 < linesCount)
	{
		auto n = roundNumber - a;
		int digitsCount = max(4, digitsInAddr + 1);
		if (n >= linesCount)
		{
			result += linesCount * digitsCount;
			break;
		}
		result += n * digitsCount;
		linesCount -= n;

		a = roundNumber;
		roundNumber <<= 4;
		++digitsInAddr;
	}
	// add place for the NULL character (+1)
	return result + 1;
}

int CopyConverterWysiwyg::GetEstimatedBytesPerLine() const
{
	return 16;
}

CopyConverter::ErrorCode 
	CopyConverterWysiwyg::WriteData(const BYTE* inPtr, int& ioByteCount)
{
	ErrorCode result = kWriteFail;
	int digits = 0;
	for (auto x = mStartAddress; x; x >>= 4)
	{
		++digits;
	}
	if (digits < 4)
	{
		digits = 4;
	}

	// address digits + ": " + 16-bytes + " -" in the middle + "  " + 16 symbols + CRLF
	int lineLen1 = digits + 2 + 3 * 16 + 2 + 2 + 16 + 2;
	if (BYTE* text = GetBufferOfSize(lineLen1))
	{
		const int skippedBytes = mStartAddress & 15;
		const int endIndex = min(16, (ioByteCount + skippedBytes));
		auto pByte0 = inPtr - skippedBytes;

		BYTE* p = WriteAddress(text, (mStartAddress - skippedBytes));
		*p++ = ':';
		*p++ = ' ';
		for (int i = 0; i < 16; ++i)
		{
			*p++ = ' ';
			if (skippedBytes <= i && i < endIndex)
			{
				p = WriteByteHex(p, pByte0[i]);
			}
			else
			{
				*p++ = ' ';
				*p++ = ' ';
			}
			if (7 == i)
			{
				*p++ = ' ';
				*p++ = '-';
			}
		}
		*p++ = ' ';
		*p++ = ' ';
		for (int i = 0; i < 16; ++i)
		{
			BYTE ch = ' ';
			if (skippedBytes <= i && i < endIndex)
			{
				ch = pByte0[i];
				if (ch < 0x20 || ch >= 0x7f)
				{
					ch = '.';
				}
			}
			*p++ = ch;
		}
	
		*p++ = '\r';
		*p++ = '\n';
		SaveUpToBufferOffset(p - text);
		ioByteCount = endIndex - skippedBytes;
		mStartAddress += ioByteCount;
		result = kOk;
	}
	return result;
}

// --

LONGLONG CopyConverterHexDigits::CalculateDataLength(LONGLONG inInputLength) const
{
	auto lineCount = (inInputLength + kBytesPerRowInHexDigitsMode - 1)
		/ kBytesPerRowInHexDigitsMode;

	// three charactes per byte (2 hex digits and space) and two 
	// per line (CR, LF), minus the trailing space at the line end
	// also, there should be place for terminating NULL character (+1)
	return inInputLength * 3 + lineCount + 1;
}

int CopyConverterHexDigits::GetEstimatedBytesPerLine() const
{
	return kBytesPerRowInHexDigitsMode;
}

CopyConverter::ErrorCode
   CopyConverterHexDigits::WriteData(const BYTE* ptr, int& ioByteCount)
{
	ErrorCode result = kWriteFail;
	int n = min(ioByteCount, kBytesPerRowInHexDigitsMode);
	int numberOfCharacters = 3 * n + 1;
	if (auto text = GetBufferOfSize(numberOfCharacters))
	{
		auto p = text;
		for (int i = 0; i < n; ++i)
		{
			p = WriteByteHex(p, ptr[i]);
			*p++ = ' ';
		}
		p[-1] = '\r'; // overwrite the traing space character
		*p++ = '\n';
		SaveUpToBufferOffset(p - text);
		ioByteCount = n;
		result = kOk;
	}
	return result;
}

// ------------
bool CopyConverterCStyleAscii::InitWithSelectionLength(LONGLONG selectionLength,
	LONGLONG& outMinDataLength, LONGLONG& outMaxDataLength)
{
	const int bytesPerLine = GetEstimatedBytesPerLine();
	auto x = selectionLength;
	// additional 4 characters per line: two qoute symbols, CR, LF
	// (also there is a null charcter at the end, +1)
	outMinDataLength = x + (4 * x / bytesPerLine) + 4 + 1;
	x = 4 * x;
	outMaxDataLength = x + (4 * x / bytesPerLine) + 4 + 1;
	return InitBuffersForSizes(outMinDataLength, outMaxDataLength);
}

int CopyConverterCStyleAscii::GetEstimatedBytesPerLine() const
{
	return kApproximateLineLengthLimitInCStyleMode - 2;
}

CopyConverter::ErrorCode
	CopyConverterCStyleAscii::WriteData(const BYTE* ptr, int& ioByteCount)
{
	ErrorCode result = kWriteFail;
	int writtenBytesCount = 0;
	if (auto text = GetBufferOfSize(kApproximateLineLengthLimitInCStyleMode + 2))
	{
		auto p = text;
		auto pEnd = text + kApproximateLineLengthLimitInCStyleMode - 1;
		*p++ = '\"';
		for (; writtenBytesCount < ioByteCount; ++writtenBytesCount)
		{
			BYTE ch = ptr[writtenBytesCount];
			if (0x20 <= ch && ch < 0x7f)
			{
				if ('\'' == ch || '\"' == ch || '\\' == ch)
				{
					if (p + 2 >= pEnd)
					{
						break;
					}
					*p++ = '\\';
				}
				else if (p + 1 >= pEnd)
				{
					break;
				}
				*p++ = ch;
			}
			else
			{
				static const char kEscapedChars[] = "\t\r\n";
				static const char kSecondSymbol[] = "trn";
				
				auto pChar = ::strchr(kEscapedChars, ch);
				if (nullptr != pChar && '\0' != ch)
				{
					if (p + 2 >= pEnd)
					{
						break;
					}
					*p++ = '\\';
					*p++ = kSecondSymbol[pChar - kEscapedChars];
				}
				else
				{
					if (p + 4 >= pEnd)
					{
						break;
					}
					*p++ = '\\';
					p = WriteByteOct(p, ch);
				}
			}
		}
		*p++ = '\"';
		*p++ = '\r';
		*p++ = '\n';
		SaveUpToBufferOffset(p - text);
		result = kOk;
	}
	ioByteCount = writtenBytesCount;
	return result;
}

// ------

bool CopyConverterUtf8::InitWithSelectionLength(LONGLONG selectionLength,
	LONGLONG& outMinDataLength, LONGLONG& outMaxDataLength)
{
	// the biggest number of bytes per character is 4 (in UTF-8)
	outMinDataLength = sizeof(WCHAR) * (selectionLength / 4 + 1 + 1);

	// account for terminating NULL character
	outMaxDataLength = sizeof(WCHAR) * (selectionLength + 1);
	mCount = mRemainingCount = 0;
	return InitBuffersForSizes(outMinDataLength, outMaxDataLength);
}

int CopyConverterUtf8::GetEstimatedBytesPerLine() const
{
	// some reasonable value, less than the block size
	return 100;
}

CopyConverter::ErrorCode
	CopyConverterUtf8::WriteData(const BYTE* inPtr, int& ioByteCount)
{
	ErrorCode error = kWriteFail;
	int length = 2 * ioByteCount;
	if (auto p0 = GetBufferOfSize(length))
	{
		char16_t* pUtf16 = reinterpret_cast<char16_t *>(p0);
		auto pByte = inPtr;
		auto pByteEnd = pByte + ioByteCount;
		int offsetFromTheFirstByte = 0;
		error = kOk;
		while (pByte < pByteEnd)
		{
			BYTE b = *pByte++;
			if (0 == mRemainingCount)
			{
				if (b < 0x80)
				{
					if (0 == b)
					{
						error = kNullCharacter;
						break;
					}
					*pUtf16++ = b;
				}
				else if (b < 0xc0)
				{
					offsetFromTheFirstByte = 1;
					error = kBadCharacter;
					break;
				}
				else if (b < 0xe0)
				{
					mCharacter = b & 0x1f;
					mCount = mRemainingCount = 1;
				}
				else if (b < 0xf0)
				{
					mCharacter = b & 0xf;
					mCount = mRemainingCount = 2;
				}
				else if (b < 0xf8)
				{
					mCharacter = b & 7;
					mCount = mRemainingCount = 3;
				}
				else
				{
					offsetFromTheFirstByte = 1;
					error = kBadCharacter;
					break;
				}
			}
			else
			{
				if (0x80 <= b && b < 0xc0)
				{
					mCharacter = (mCharacter << 6) + (b & 0x3f);
					if (0 == --mRemainingCount)
					{
						static const char32_t kMinValues[] = { 0x80, 0x800, 0x10000 };
						if (kMinValues[mCount - 1] <= mCharacter &&  mCharacter < 0x10ffff)
						{
							if (mCharacter < 0x10000)
							{
								if (0xd800 <= mCharacter && mCharacter < 0xe000)
								{
									offsetFromTheFirstByte = mCount + 1;
									error = kBadCharacter;
									break;
								}
								*pUtf16++ = (char16_t)mCharacter;
							}
							else
							{
								*pUtf16++ = (mCharacter >> 10) + 0xd800 - 64;
								*pUtf16++ = (mCharacter & 0x3ff) + 0xdc00;
							}
						}
						else
						{
							offsetFromTheFirstByte = mCount + 1;
							error = kBadCharacter;
							break;
						}
					}
				}
				else
				{
					offsetFromTheFirstByte = mCount - mRemainingCount + 2;
					error = kBadCharacter;
					break;
				}
			}
		}

		
		if (kOk == error)
		{
			if (0 < mRemainingCount)
			{
				error = kIncompleteCharacter;
			}
		}
		else if (kBadCharacter == error)
		{
			// the offset will point to the first byte in sequence
			pByte -= offsetFromTheFirstByte;
		}

		int offset = reinterpret_cast<BYTE*>(pUtf16) - p0;
		SaveUpToBufferOffset(offset);

		ioByteCount = pByte - inPtr;
	}
	return error;
}

bool CopyConverterUtf8::IsWideCharacterData() const
{
	return true;
}

// ---------------------------------------------------

CopyConverterUtf16Base::CopyConverterUtf16Base()
{
	mIndex = 0;
	mWasFirstSurrogate = false;
}

LONGLONG CopyConverterUtf16Base::CalculateDataLength(LONGLONG inInputLength) const
{
	// strip the odd byte (if present); add the terminating NULL characer (two bytes)
	return (inInputLength & (~1)) + 2;
}

int CopyConverterUtf16Base::GetEstimatedBytesPerLine() const
{
	return 100;
}

CopyConverter::ErrorCode
	CopyConverterUtf16Base::WriteData(const BYTE* inPtr, int& ioByteCount)
{
	ErrorCode error = kWriteFail;
	if (BYTE* pBuffer = GetBufferOfSize(ioByteCount + 1))
	{
		auto pWchar = reinterpret_cast<char16_t *>(pBuffer);
		error = kOk;
		for (int i = 0; i < ioByteCount; ++i)
		{
			if (0 == (1 & mIndex))
			{
				mPreviousByte = inPtr[i];
			}
			else
			{
				char16_t ch = ComposeWord(mPreviousByte, inPtr[i]);
				if (mWasFirstSurrogate)
				{
					if (0xdc00 > ch || ch >= 0xe00)
					{
						error = kBadCharacter;
						ioByteCount = i - 3;
						break;
					}
					mWasFirstSurrogate = false;
				}
				else
				{
					if ('0' == ch)
					{
						error = kNullCharacter;
						ioByteCount = i - 1;
						break;
					}
					if (ch >= 0xd800)
					{
						if (ch < 0xdc00)
						{
							mWasFirstSurrogate = true;
						}
						else if (ch < 0xe000)
						{
							error = kBadCharacter;
							ioByteCount = i - 1;
							break;
						}
					}
				}
				*pWchar++ = ch;
			}
			++mIndex;
		}
		SaveUpToBufferOffset(reinterpret_cast<BYTE*>(pWchar) - pBuffer);
		if (kOk == error && 1 == (1 & mIndex))
		{
			error = kIncompleteCharacter;
		}
	}
	return error;
}

bool CopyConverterUtf16Base::IsWideCharacterData() const
{
	return true;
}

// ---
char16_t CopyConverterUtf16LE::ComposeWord(BYTE inByte1, BYTE inByte2)
{
	return (inByte2 << 8) + inByte1;
}

char16_t CopyConverterUtf16BE::ComposeWord(BYTE inByte1, BYTE inByte2)
{
	return (inByte1 << 8) + inByte2;
}